package bjut.core.extension;

import bjut.core.annotation.SPI;
import bjut.core.bean.Holder;
import lombok.SneakyThrows;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Author: zhuzhiming
 * @Date: 2022/4/18 0:18
 */
public class ExtensionLoader02<T> {

    private static final ConcurrentHashMap<Class<?>, ExtensionLoader02<?>> EXTENSION_LOADER_MAP = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<String, Holder<Object>> holderMap = new ConcurrentHashMap<>();
    private static final ConcurrentHashMap<Class<?>,Object> EXTENSION_INSTANCE_MAP = new ConcurrentHashMap<>();

    private final Holder<Map<String, Class<?>>> holderCache = new Holder<>();

    private static final String ROOT_PATH = "META-INF/extensions/";

    private Class<T> type;

    private ExtensionLoader02(Class<T> type) {
        this.type = type;
    }

    public ExtensionLoader02<T> getExtensionLoader(Class<T> type) {

        if (type == null) throw new NullPointerException("type must not be null");

        if (!type.isInterface()) throw new IllegalArgumentException("type must be interface");

        if (!type.isAnnotationPresent(SPI.class)) throw new IllegalArgumentException("type must has SPI annotation");

        ExtensionLoader02<T> extensionLoader02 = (ExtensionLoader02<T>) EXTENSION_LOADER_MAP.get(type);
        if (extensionLoader02 == null) {
            EXTENSION_LOADER_MAP.put(type, new ExtensionLoader02<>(type));
            extensionLoader02 = (ExtensionLoader02<T>) EXTENSION_LOADER_MAP.get(type);
        }
        return extensionLoader02;
    }

    public T getExtension(String name) {

        if (name.trim().length() == 0) throw new IllegalArgumentException("Extension name must not be empty");

        Holder<Object> holder = holderMap.get(name);

        if (holder == null) {
            holderMap.put(name, new Holder<>());
            holder = holderMap.get(name);
        }
        Object holderValue = holder.getValue();
        if (holderValue == null) {
            synchronized (this) {
                holderValue = holder.getValue();
                if (holderValue == null) {
                    holderValue = createExtension(name);
                    holder.setValue(holderValue);
                }
            }
        }
        return (T) holderValue;
    }

    @SneakyThrows
    private Object createExtension(String name) {

        Map<String, Class<?>> classMap = holderCache.getValue();

        if (classMap == null) {
            synchronized (this) {
                classMap = holderCache.getValue();
                if (classMap == null) {
                    classMap = new HashMap<>();
                    loadDirectory(classMap);
                }
            }
        }
        Class<?> aClass = classMap.get(name);
        Object instance = EXTENSION_INSTANCE_MAP.get(aClass);
        if(null == instance){
            EXTENSION_INSTANCE_MAP.putIfAbsent(aClass,aClass.newInstance());
            instance = EXTENSION_INSTANCE_MAP.get(aClass);
        }

         return instance;

    }

    @SneakyThrows
    private void loadDirectory(Map<String, Class<?>> classMap) {

        String filePath = ROOT_PATH + type.getName();

        ClassLoader classLoader = ExtensionLoader02.class.getClassLoader();
        Enumeration<URL> resources = classLoader.getResources(filePath);
        URL url;
        while (resources.hasMoreElements()) {
            url = resources.nextElement();
            loadResource(classMap, classLoader, url);
        }


    }

    @SneakyThrows
    private void loadResource(Map<String, Class<?>> classMap, ClassLoader classLoader, URL url) {

        try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(url.openStream(), StandardCharsets.UTF_8))) {

            String line = "";

            while ((line = bufferedReader.readLine()) != null) {
                line = line.trim();
                int commentIndex = line.indexOf("#");
                if (commentIndex > 0) {
                    line = line.substring(0, commentIndex);
                }

                String key = line.substring(line.indexOf("="));
                String value = line.substring(line.indexOf("=") + 1);

                Class<?> aClass = classLoader.loadClass(value);
                classMap.put(key, aClass);

            }


        } catch (Exception e) {
            System.out.println(e.getMessage());
        }


    }

}
